From 214520455157307d42fc24bbb9964771336cc8a2 Mon Sep 17 00:00:00 2001
From: "twilkie@paploo.uk.xensource.com"
Date: Tue, 26 Jul 2005 13:56:30 +0000
Subject: [PATCH] Add XenSV back into the repository. Now uses Python Server
Pages, not twisted.
---
tools/Makefile | 1 +
tools/python/setup.py | 1 +
tools/python/xen/sv/CreateDomain.py | 163 +++++++++++
tools/python/xen/sv/Daemon.py | 110 +++++++
tools/python/xen/sv/DomInfo.py | 148 ++++++++++
tools/python/xen/sv/DomList.py | 81 ++++++
tools/python/xen/sv/GenTabbed.py | 135 +++++++++
tools/python/xen/sv/HTMLBase.py | 62 ++++
tools/python/xen/sv/Main.py | 113 ++++++++
tools/python/xen/sv/MigrateDomain.py | 74 +++++
tools/python/xen/sv/NodeInfo.py | 63 ++++
tools/python/xen/sv/RestoreDomain.py | 46 +++
tools/python/xen/sv/SaveDomain.py | 62 ++++
tools/python/xen/sv/TabView.py | 26 ++
tools/python/xen/sv/Wizard.py | 269 ++++++++++++++++++
tools/python/xen/sv/__init__.py | 1 +
tools/python/xen/sv/params.py | 3 +
tools/python/xen/sv/util.py | 126 ++++++++
tools/sv/Makefile | 2 +
tools/sv/images/destroy.png | Bin 0 -> 2408 bytes
tools/sv/images/finish.png | Bin 0 -> 1189 bytes
tools/sv/images/internet copy.jpg | Bin 0 -> 67456 bytes
tools/sv/images/internet.jpg | Bin 0 -> 37866 bytes
tools/sv/images/internet.psd | Bin 0 -> 1584508 bytes
tools/sv/images/next.png | Bin 0 -> 1270 bytes
tools/sv/images/orb_01.jpg | Bin 0 -> 19864 bytes
tools/sv/images/orb_02.jpg | Bin 0 -> 507 bytes
tools/sv/images/pause.png | Bin 0 -> 1662 bytes
tools/sv/images/previous.png | Bin 0 -> 1285 bytes
tools/sv/images/reboot.png | Bin 0 -> 3132 bytes
tools/sv/images/seperator-left-highlight.jpg | Bin 0 -> 552 bytes
tools/sv/images/seperator-right-highlight.jpg | Bin 0 -> 560 bytes
tools/sv/images/seperator.jpg | Bin 0 -> 443 bytes
tools/sv/images/shutdown.png | Bin 0 -> 2901 bytes
tools/sv/images/small-destroy.png | Bin 0 -> 483 bytes
tools/sv/images/small-pause.png | Bin 0 -> 434 bytes
tools/sv/images/small-unpause.png | Bin 0 -> 500 bytes
tools/sv/images/unpause.png | Bin 0 -> 1890 bytes
tools/sv/images/xen.png | Bin 0 -> 10575 bytes
tools/sv/inc/script.js | 31 ++
tools/sv/inc/style.css | 32 +++
tools/sv/index.psp | 164 +++++++++++
42 files changed, 1713 insertions(+)
create mode 100755 tools/python/xen/sv/CreateDomain.py
create mode 100755 tools/python/xen/sv/Daemon.py
create mode 100755 tools/python/xen/sv/DomInfo.py
create mode 100755 tools/python/xen/sv/DomList.py
create mode 100755 tools/python/xen/sv/GenTabbed.py
create mode 100755 tools/python/xen/sv/HTMLBase.py
create mode 100755 tools/python/xen/sv/Main.py
create mode 100755 tools/python/xen/sv/MigrateDomain.py
create mode 100755 tools/python/xen/sv/NodeInfo.py
create mode 100755 tools/python/xen/sv/RestoreDomain.py
create mode 100755 tools/python/xen/sv/SaveDomain.py
create mode 100755 tools/python/xen/sv/TabView.py
create mode 100755 tools/python/xen/sv/Wizard.py
create mode 100755 tools/python/xen/sv/__init__.py
create mode 100755 tools/python/xen/sv/params.py
create mode 100755 tools/python/xen/sv/util.py
create mode 100644 tools/sv/Makefile
create mode 100755 tools/sv/images/destroy.png
create mode 100755 tools/sv/images/finish.png
create mode 100755 tools/sv/images/internet copy.jpg
create mode 100755 tools/sv/images/internet.jpg
create mode 100755 tools/sv/images/internet.psd
create mode 100755 tools/sv/images/next.png
create mode 100755 tools/sv/images/orb_01.jpg
create mode 100755 tools/sv/images/orb_02.jpg
create mode 100755 tools/sv/images/pause.png
create mode 100755 tools/sv/images/previous.png
create mode 100755 tools/sv/images/reboot.png
create mode 100755 tools/sv/images/seperator-left-highlight.jpg
create mode 100755 tools/sv/images/seperator-right-highlight.jpg
create mode 100755 tools/sv/images/seperator.jpg
create mode 100755 tools/sv/images/shutdown.png
create mode 100755 tools/sv/images/small-destroy.png
create mode 100755 tools/sv/images/small-pause.png
create mode 100755 tools/sv/images/small-unpause.png
create mode 100755 tools/sv/images/unpause.png
create mode 100755 tools/sv/images/xen.png
create mode 100755 tools/sv/inc/script.js
create mode 100755 tools/sv/inc/style.css
create mode 100755 tools/sv/index.psp
diff --git a/tools/Makefile b/tools/Makefile
index 00eb4991cc..95b63c36ba 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -13,6 +13,7 @@ SUBDIRS += xcutils
SUBDIRS += pygrub
SUBDIRS += firmware
SUBDIRS += policy
+SUBDIRS += sv
.PHONY: all install clean check check_clean ioemu eioemuinstall ioemuclean
diff --git a/tools/python/setup.py b/tools/python/setup.py
index e2a5b09ca1..c2c5448d5e 100644
--- a/tools/python/setup.py
+++ b/tools/python/setup.py
@@ -51,6 +51,7 @@ setup(name = 'xen',
'xen.xend.xenstore',
'xen.xm',
'xen.web',
+ 'xen.sv'
],
ext_package = "xen.lowlevel",
ext_modules = [ xc, xu, xs ]
diff --git a/tools/python/xen/sv/CreateDomain.py b/tools/python/xen/sv/CreateDomain.py
new file mode 100755
index 0000000000..4378897e5c
--- /dev/null
+++ b/tools/python/xen/sv/CreateDomain.py
@@ -0,0 +1,163 @@
+from xen.sv.Wizard import *
+from xen.sv.util import *
+from xen.sv.GenTabbed import PreTab
+
+from xen.xm.create import make_config, OptVals
+
+from xen.xend.XendClient import server
+
+class CreateDomain( Wizard ):
+ def __init__( self, urlWriter ):
+
+ sheets = [ CreatePage0,
+ CreatePage1,
+ CreatePage2,
+ CreatePage3,
+ CreatePage4,
+ CreateFinish ]
+
+ Wizard.__init__( self, urlWriter, "Create Domain", sheets )
+
+class CreatePage0( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "General", 0 )
+ self.addControl( InputControl( 'name', 'VM Name', 'VM Name:', "[\\w|\\S]+", "You must enter a name in this field" ) )
+ self.addControl( InputControl( 'memory', '64', 'Memory (Mb):', "[\\d]+", "You must enter a number in this field" ) )
+ self.addControl( InputControl( 'cpu', '0', 'CPU:', "[\\d]+", "You must enter a number in this feild" ) )
+ self.addControl( InputControl( 'cpu_weight', '1', 'CPU Weight:', "[\\d]+", "You must enter a number in this feild" ) )
+
+class CreatePage1( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Setup Kernel Image", 1 )
+# For now we don't need to select a builder...
+# self.addControl( ListControl( 'builder', [('linux', 'Linux'), ('netbsd', 'NetBSD')], 'Kernel Type:' ) )
+ self.addControl( FileControl( 'kernel', '/boot/vmlinuz-2.6.9-xenU', 'Kernel Image:' ) )
+ self.addControl( InputControl( 'extra', '', 'Kernel Command Line Parameters:' ) )
+
+class CreatePage2( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Setup Virtual Block Device", 2 )
+ self.addControl( InputControl( 'num_vbds', '1', 'Number of VBDs:', '[\\d]+', "You must enter a number in this field" ) )
+
+class CreatePage3( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Setup Virtual Block Device", 3 )
+
+ def write_BODY( self, request, err ):
+ if not self.passback: self.parseForm( request )
+
+ previous_values = sxp2hash( string2sxp( self.passback ) ) #get the hash for quick reference
+
+ num_vbds = previous_values.get( 'num_vbds' )
+
+ for i in range( int( num_vbds ) ):
+ self.addControl( InputControl( 'vbd%s_dom0' % i, 'phy:sda%s' % str(i + 1), 'Device %s name:' % i ) )
+ self.addControl( InputControl( 'vbd%s_domU' % i, 'sda%s' % str(i + 1), 'Virtualized device %s:' % i ) )
+ self.addControl( ListControl( 'vbd%s_mode' % i, [('w', 'Read + Write'), ('r', 'Read Only')], 'Device %s mode:' % i ) )
+
+ self.addControl( InputControl( 'root', '/dev/sda1', 'Root device (in VM):' ) )
+
+ Sheet.write_BODY( self, request, err )
+
+class CreatePage4( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Network settings", 4 )
+ self.addControl( ListControl( 'dhcp', [('off', 'No'), ('dhcp', 'Yes')], 'Use DHCP:' ) )
+ self.addControl( InputControl( 'hostname', 'hostname', 'VM Hostname:' ) )
+ self.addControl( InputControl( 'ip_addr', '1.2.3.4', 'VM IP Address:' ) )
+ self.addControl( InputControl( 'ip_subnet', '255.255.255.0', 'VM Subnet Mask:' ) )
+ self.addControl( InputControl( 'ip_gateway', '1.2.3.4', 'VM Gateway:' ) )
+ self.addControl( InputControl( 'ip_nfs', '1.2.3.4', 'NFS Server:' ) )
+
+class CreateFinish( Sheet ):
+
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "All Done", 5 )
+
+ def write_BODY( self, request, err ):
+
+ if not self.passback: self.parseForm( request )
+
+ xend_sxp = self.translate_sxp( string2sxp( self.passback ) )
+
+ try:
+ dom_sxp = server.xend_domain_create( xend_sxp )
+ success = "Your domain was successfully created.\n"
+ except:
+ success = "There was an error creating your domain.\nThe configuration used is as follows:\n"
+ dom_sxp = xend_sxp
+
+
+
+ pt = PreTab( success + sxp2prettystring( dom_sxp ) )
+ pt.write_BODY( request )
+
+ request.write( "
" % self.passback )
+ request.write( "" % self.location )
+
+ def translate_sxp( self, fin_sxp ):
+ fin_hash = ssxp2hash( fin_sxp )
+
+ def get( key ):
+ ret = fin_hash.get( key )
+ if ret:
+ return ret
+ else:
+ return ""
+
+ vals = OptVals()
+
+ vals.name = get( 'name' )
+ vals.memory = get( 'memory' )
+ vals.maxmem = get( 'maxmem' )
+ vals.cpu = get( 'cpu' )
+ vals.cpu_weight = get( 'cpu_weight' )
+
+ vals.builder = get( 'builder' )
+ vals.kernel = get( 'kernel' )
+ vals.root = get( 'root' )
+ vals.extra = get( 'extra' )
+
+ #setup vbds
+
+ vbds = []
+
+ for i in range( int( get( 'num_vbds' ) ) ):
+ vbds.append( ( get( 'vbd%s_dom0' % i ), get('vbd%s_domU' % i ), get( 'vbd%s_mode' % i ) ) )
+
+ vals.disk = vbds
+
+ #misc
+
+ vals.pci = []
+
+ vals.blkif = None
+ vals.netif = None
+ vals.restart = None
+ vals.console = None
+ vals.ramdisk = None
+
+ #setup vifs
+
+ vals.vif = []
+ vals.nics = 1
+
+ ip = get( 'ip_addr' )
+ nfs = get( 'ip_nfs' )
+ gate = get( 'ip_gateway' )
+ mask = get( 'ip_subnet' )
+ host = get( 'hostname' )
+ dhcp = get( 'dhcp' )
+
+ vals.cmdline_ip = "%s:%s:%s:%s:%s:eth0:%s" % (ip, nfs, gate, mask, host, dhcp)
+
+ try:
+ return make_config( vals )
+ except:
+ return [["Error creating domain config."]]
+
diff --git a/tools/python/xen/sv/Daemon.py b/tools/python/xen/sv/Daemon.py
new file mode 100755
index 0000000000..5a8d18e5e4
--- /dev/null
+++ b/tools/python/xen/sv/Daemon.py
@@ -0,0 +1,110 @@
+###########################################################
+## XenSV Web Control Interface Daemon
+## Copyright (C) 2004, K A Fraser (University of Cambridge)
+## Copyright (C) 2004, Mike Wray
+## Copyright (C) 2004, Tom Wilkie
+###########################################################
+
+import os
+import os.path
+import sys
+import re
+
+from xen.sv.params import *
+
+from twisted.internet import reactor
+from twisted.web import static, server, script
+
+from xen.util.ip import _readline, _readlines
+
+class Daemon:
+ """The xend daemon.
+ """
+ def __init__(self):
+ self.shutdown = 0
+ self.traceon = 0
+
+ def daemon_pids(self):
+ pids = []
+ pidex = '(?P\d+)'
+ pythonex = '(?P\S*python\S*)'
+ cmdex = '(?P.*)'
+ procre = re.compile('^\s*' + pidex + '\s*' + pythonex + '\s*' + cmdex + '$')
+ xendre = re.compile('^/usr/sbin/xend\s*(start|restart)\s*.*$')
+ procs = os.popen('ps -e -o pid,args 2>/dev/null')
+ for proc in procs:
+ pm = procre.match(proc)
+ if not pm: continue
+ xm = xendre.match(pm.group('cmd'))
+ if not xm: continue
+ #print 'pid=', pm.group('pid'), 'cmd=', pm.group('cmd')
+ pids.append(int(pm.group('pid')))
+ return pids
+
+ def new_cleanup(self, kill=0):
+ err = 0
+ pids = self.daemon_pids()
+ if kill:
+ for pid in pids:
+ print "Killing daemon pid=%d" % pid
+ os.kill(pid, signal.SIGHUP)
+ elif pids:
+ err = 1
+ print "Daemon already running: ", pids
+ return err
+
+ def cleanup(self, kill=False):
+ # No cleanup to do if PID_FILE is empty.
+ if not os.path.isfile(PID_FILE) or not os.path.getsize(PID_FILE):
+ return 0
+ # Read the pid of the previous invocation and search active process list.
+ pid = open(PID_FILE, 'r').read()
+ lines = _readlines(os.popen('ps ' + pid + ' 2>/dev/null'))
+ for line in lines:
+ if re.search('^ *' + pid + '.+xensv', line):
+ if not kill:
+ print "Daemon is already running (pid %d)" % int(pid)
+ return 1
+ # Old daemon is still active: terminate it.
+ os.kill(int(pid), 1)
+ # Delete the stale PID_FILE.
+ os.remove(PID_FILE)
+ return 0
+
+ def start(self, trace=0):
+ if self.cleanup(kill=False):
+ return 1
+
+ # Fork -- parent writes PID_FILE and exits.
+ pid = os.fork()
+ if pid:
+ # Parent
+ pidfile = open(PID_FILE, 'w')
+ pidfile.write(str(pid))
+ pidfile.close()
+ return 0
+ # Child
+ self.run()
+ return 0
+
+ def stop(self):
+ return self.cleanup(kill=True)
+
+ def run(self):
+ root = static.File( SV_ROOT )
+ root.indexNames = [ 'Main.rpy' ]
+ root.processors = { '.rpy': script.ResourceScript }
+ reactor.listenTCP( SV_PORT, server.Site( root ) )
+ reactor.run()
+
+ def exit(self):
+ reactor.disconnectAll()
+ sys.exit(0)
+
+def instance():
+ global inst
+ try:
+ inst
+ except:
+ inst = Daemon()
+ return inst
diff --git a/tools/python/xen/sv/DomInfo.py b/tools/python/xen/sv/DomInfo.py
new file mode 100755
index 0000000000..e0cabbd209
--- /dev/null
+++ b/tools/python/xen/sv/DomInfo.py
@@ -0,0 +1,148 @@
+from xen.xend.XendClient import server
+from xen.xend import PrettyPrint
+
+from xen.sv.HTMLBase import HTMLBase
+from xen.sv.util import *
+from xen.sv.GenTabbed import *
+
+DEBUG=1
+
+class DomInfo( GenTabbed ):
+
+ def __init__( self, urlWriter ):
+
+ self.dom = 0;
+
+ def tabUrlWriter( tab ):
+ return urlWriter( "&dom=%s%s" % ( self.dom, tab ) )
+
+ GenTabbed.__init__( self, "Domain Info", tabUrlWriter, [ 'General', 'SXP', 'Devices' ], [ DomGeneralTab, DomSXPTab, NullTab ] )
+
+ def write_BODY( self, request ):
+ dom = request.args.get('dom')
+
+ if dom is None or len(dom) != 1:
+ request.write( "Please Select a Domain
" )
+ return None
+ else:
+ self.dom = dom[0]
+
+ GenTabbed.write_BODY( self, request )
+
+ def write_MENU( self, request ):
+ pass
+
+class DomGeneralTab( CompositeTab ):
+ def __init__( self ):
+ CompositeTab.__init__( self, [ DomGenTab, DomActionTab ] )
+
+class DomGenTab( GeneralTab ):
+
+ def __init__( self ):
+
+ titles = {}
+
+ titles[ 'ID' ] = 'dom'
+ titles[ 'Name' ] = 'name'
+ titles[ 'CPU' ] = 'cpu'
+ titles[ 'Memory' ] = ( 'mem', memoryFormatter )
+ titles[ 'State' ] = ( 'state', stateFormatter )
+ titles[ 'Total CPU' ] = ( 'cpu_time', smallTimeFormatter )
+ titles[ 'Up Time' ] = ( 'up_time', bigTimeFormatter )
+
+ GeneralTab.__init__( self, {}, titles )
+
+ def write_BODY( self, request ):
+
+ self.dom = getVar('dom', request)
+
+ if self.dom is None:
+ request.write( "Please Select a Domain
" )
+ return None
+
+ self.dict = getDomInfoHash( self.dom )
+
+ GeneralTab.write_BODY( self, request )
+
+class DomSXPTab( PreTab ):
+
+ def __init__( self ):
+ self.dom = 0
+ PreTab.__init__( self, "" )
+
+
+ def write_BODY( self, request ):
+ self.dom = getVar('dom', request)
+
+ if self.dom is None:
+ request.write( "Please Select a Domain
" )
+ return None
+
+ try:
+ domInfo = server.xend_domain( self.dom )
+ except:
+ domInfo = [["Error getting domain details."]]
+
+ self.source = sxp2prettystring( domInfo )
+
+ PreTab.write_BODY( self, request )
+
+class DomActionTab( ActionTab ):
+
+ def __init__( self ):
+ actions = { "shutdown" : "shutdown",
+ "reboot" : "reboot",
+ "pause" : "pause",
+ "unpause" : "unpause",
+ "destroy" : "destroy" }
+ ActionTab.__init__( self, actions )
+
+ def op_shutdown( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != '0':
+ if DEBUG: print ">DomShutDown %s" % dom
+ try:
+ server.xend_domain_shutdown( int( dom ), "halt" )
+ except:
+ pass
+
+ def op_reboot( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != '0':
+ if DEBUG: print ">DomReboot %s" % dom
+ try:
+ server.xend_domain_shutdown( int( dom ), "reboot" )
+ except:
+ pass
+
+ def op_pause( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != '0':
+ if DEBUG: print ">DomPause %s" % dom
+ try:
+ server.xend_domain_pause( int( dom ) )
+ except:
+ pass
+
+ def op_unpause( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != '0':
+ if DEBUG: print ">DomUnpause %s" % dom
+ try:
+ server.xend_domain_unpause( int( dom ) )
+ except:
+ pass
+
+ def op_destroy( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != '0':
+ if DEBUG: print ">DomDestroy %s" % dom
+ try:
+ server.xend_domain_destroy( int( dom ), "halt" )
+ except:
+ pass
+
+
+
+
+
diff --git a/tools/python/xen/sv/DomList.py b/tools/python/xen/sv/DomList.py
new file mode 100755
index 0000000000..c3951122d8
--- /dev/null
+++ b/tools/python/xen/sv/DomList.py
@@ -0,0 +1,81 @@
+from xen.xend.XendClient import server
+from xen.xend import sxp
+
+from xen.sv.HTMLBase import HTMLBase
+from xen.sv.util import *
+
+class DomList( HTMLBase ):
+
+ isLeaf = True
+
+ def __init__( self, urlWriter ):
+ HTMLBase.__init__(self)
+ self.urlWriter = urlWriter
+
+ def write_MENU( self, request ):
+ return self.write_BODY( request, head=True, long=False )
+
+ def write_BODY( self, request, head=True, long=True ):
+
+ domains = []
+
+ try:
+ domains = server.xend_domains()
+ domains.sort()
+ except:
+ pass
+
+ request.write( "\n\n" )
+
+ if head:
+ request.write( "" )
+ self.write_DOMAIN_HEAD( request, long )
+ request.write( "
" )
+
+ odd = True
+
+ if not domains is None:
+ for domain in domains:
+ if odd:
+ request.write( "\n" )
+ odd = False
+ else:
+ request.write( "
\n" )
+ odd = True
+ self.write_DOMAIN( request, getDomInfoHash( domain ), long )
+ request.write( "
\n" )
+ else:
+ request.write( "Error getting domain list
Perhaps XenD not running?
")
+
+ request.write( "
\n" )
+
+ def write_DOMAIN( self, request, domInfoHash, long=True ):
+ request.write( "%(id)s | \n" % domInfoHash )
+
+ url = self.urlWriter( "&mod=info&dom=%(id)s" % domInfoHash )
+
+ request.write( "%s | \n" % ( url, domInfoHash['name'] ) )
+ if long:
+ request.write( "%(memory)5s | \n" % domInfoHash )
+ request.write( "%(cpu)2s | \n" % domInfoHash )
+ request.write( "%(state)5s | \n" % domInfoHash )
+ if domInfoHash[ 'id' ] != "0":
+ request.write( "" )
+
+ if domInfoHash[ 'state' ][ 2 ] == "-":
+ request.write( " " % domInfoHash )
+ else:
+ request.write( " " % domInfoHash )
+
+ request.write( " | " % domInfoHash)
+ else:
+ request.write( " | " )
+
+ def write_DOMAIN_HEAD( self, request, long=True ):
+ request.write( "Domain | \n" )
+ request.write( "Name | \n" )
+ if long:
+ request.write( "Memory / Mb | \n" )
+ request.write( "CPU | \n" )
+ request.write( "State | \n" )
+ request.write( " | \n" )
diff --git a/tools/python/xen/sv/GenTabbed.py b/tools/python/xen/sv/GenTabbed.py
new file mode 100755
index 0000000000..ce8711ce55
--- /dev/null
+++ b/tools/python/xen/sv/GenTabbed.py
@@ -0,0 +1,135 @@
+import types
+
+from xen.sv.HTMLBase import HTMLBase
+from xen.sv.TabView import TabView
+from xen.sv.util import getVar
+
+class GenTabbed( HTMLBase ):
+
+ def __init__( self, title, urlWriter, tabStrings, tabObjects ):
+ HTMLBase.__init__(self)
+ self.tabStrings = tabStrings
+ self.tabObjects = tabObjects
+ self.urlWriter = urlWriter
+ self.title = title
+
+ def write_BODY( self, request, urlWriter = None ):
+ try:
+ tab = int( getVar( 'tab', request, 0 ) )
+ except:
+ tab = 0
+
+ request.write( "" )
+ request.write( "| " )
+ request.write( " %s " % self.title )
+
+ TabView( tab, self.tabStrings, self.urlWriter ).write_BODY( request )
+
+ request.write( " |
| " )
+
+ try:
+ render_tab = self.tabObjects[ tab ]
+ render_tab().write_BODY( request )
+ except:
+ request.write( " Error Rendering Tab " )
+
+ request.write( " |
" )
+
+ def perform( self, request ):
+ try:
+ tab = int( getVar( 'tab', request, 0 ) )
+ except:
+ tab = 0;
+
+ op_tab = self.tabObjects[ tab ]
+
+ if op_tab:
+ op_tab().perform( request )
+
+class PreTab( HTMLBase ):
+
+ def __init__( self, source ):
+ HTMLBase.__init__( self )
+ self.source = source
+
+ def write_BODY( self, request ):
+
+ request.write( "" )
+
+ request.write( self.source )
+
+ request.write( "
" )
+
+class GeneralTab( HTMLBase ):
+
+ def __init__( self, dict, titles ):
+ HTMLBase.__init__( self )
+ self.dict = dict
+ self.titles = titles
+
+ def write_BODY( self, request ):
+
+ request.write( "" )
+
+ def writeAttr( niceName, attr, formatter=None ):
+ if type( attr ) is types.TupleType:
+ ( attr, formatter ) = attr
+
+ if attr in self.dict:
+ if formatter:
+ temp = formatter( self.dict[ attr ] )
+ else:
+ temp = str( self.dict[ attr ] )
+ request.write( "%s: | %s |
" % ( niceName, temp ) )
+
+ for niceName, attr in self.titles.items():
+ writeAttr( niceName, attr )
+
+ request.write( "
" )
+
+class NullTab( HTMLBase ):
+
+ def __init__( self ):
+ HTMLBase.__init__( self )
+ self.title = "Null Tab"
+
+ def __init__( self, title ):
+ HTMLBase.__init__( self )
+ self.title = title
+
+ def write_BODY( self, request ):
+ request.write( "%s
" % self.title )
+
+class ActionTab( HTMLBase ):
+
+ def __init__( self, actions ):
+ self.actions = actions
+ HTMLBase.__init__( self )
+
+ def write_BODY( self, request ):
+ request.write( "" )
+
+ for ( command, text ) in self.actions.items():
+ request.write( "| " )
+ request.write( " %s | " % (command, text) )
+
+ request.write("
")
+
+class CompositeTab( HTMLBase ):
+
+ def __init__( self, tabs ):
+ HTMLBase.__init__( self )
+ self.tabs = tabs
+
+ def write_BODY( self, request ):
+ for tab in self.tabs:
+ request.write( "
" )
+ tab().write_BODY( request )
+
+ def perform( self, request ):
+ for tab in self.tabs:
+ tab().perform( request )
+
+
+
+
diff --git a/tools/python/xen/sv/HTMLBase.py b/tools/python/xen/sv/HTMLBase.py
new file mode 100755
index 0000000000..121463e845
--- /dev/null
+++ b/tools/python/xen/sv/HTMLBase.py
@@ -0,0 +1,62 @@
+from xen.sv.util import *
+
+class HTMLBase:
+
+ isLeaf = True
+
+ def __init__( self ):
+ pass
+
+ def render_POST( self, request ):
+ self.perform( request )
+ return self.render_GET( request )
+
+ def render_GET( self, request ):
+ self.write_TOP( request )
+ self.write_BODY( request )
+ self.write_BOTTOM( request )
+ return ''
+
+ def write_BODY( self, request ):
+ request.write( "BODY" )
+
+ def write_TOP( self, request ):
+ request.write( 'Xen' )
+ request.write( '' )
+ request.write( '' )
+ request.write('')
+ request.write( "" )
+
+ def get_op_method(self, op):
+ """Get the method for an operation.
+ For operation 'foo' looks for 'op_foo'.
+
+ op operation name
+ returns method or None
+ """
+ op_method_name = 'op_' + op
+ return getattr(self, op_method_name, None)
+
+ def perform(self, req):
+ """General operation handler for posted operations.
+ For operation 'foo' looks for a method op_foo and calls
+ it with op_foo(req). Replies with code 500 if op_foo
+ is not found.
+
+ The method must return a list when req.use_sxp is true
+ and an HTML string otherwise (or list).
+ Methods may also return a Deferred (for incomplete processing).
+
+ req request
+ """
+ op = req.args.get('op')
+ if not op is None and len(op) == 1:
+ op = op[0]
+ op_method = self.get_op_method(op)
+ if op_method:
+ op_method( req )
diff --git a/tools/python/xen/sv/Main.py b/tools/python/xen/sv/Main.py
new file mode 100755
index 0000000000..196e1c1450
--- /dev/null
+++ b/tools/python/xen/sv/Main.py
@@ -0,0 +1,113 @@
+from xen.sv.HTMLBase import HTMLBase
+from xen.sv.DomList import DomList
+from xen.sv.NodeInfo import NodeInfo
+from xen.sv.DomInfo import DomInfo
+from xen.sv.CreateDomain import CreateDomain
+from xen.sv.MigrateDomain import MigrateDomain
+from xen.sv.SaveDomain import SaveDomain
+from xen.sv.RestoreDomain import RestoreDomain
+
+from xen.xend.XendClient import server
+
+from xen.sv.util import getVar
+
+class Main( HTMLBase ):
+
+ isLeaf = True
+
+ def __init__( self, urlWriter = None ):
+ self.modules = { "node": NodeInfo,
+ "list": DomList,
+ "info": DomInfo,
+ "create": CreateDomain,
+ "migrate" : MigrateDomain,
+ "save" : SaveDomain,
+ "restore" : RestoreDomain }
+
+ # ordered list of module menus to display
+ self.module_menus = [ "node", "create", "migrate", "save",
+ "restore", "list" ]
+ HTMLBase.__init__(self)
+
+ def render_POST( self, request ):
+
+ #decide what module post'd the action
+
+ args = getVar( 'args', request )
+
+ mod = getVar( 'mod', request )
+
+ if not mod is None and args is None:
+ module = self.modules[ mod ]
+ #check module exists
+ if module:
+ module( self.mainUrlWriter ).perform( request )
+ else:
+ self.perform( request )
+
+ return self.render_GET( request )
+
+ def mainUrlWriter( self, module ):
+ def fun( f ):
+ return "Main.rpy?mod=%s%s" % ( module, f )
+ return fun
+
+ def write_BODY( self, request ):
+
+ request.write( "\n\n" )
+ request.write( "\n" )
+ request.write( " | | " )
+ request.write( " " )
+ request.write( " " )
+ request.write( " " )
+ request.write( " 
| " )
+ request.write( " SV Web Interface (C) Tom Wilkie 2004 | ")
+ request.write( " | " )
+
+ for modName in self.module_menus:
+ self.modules[modName]( self.mainUrlWriter( modName ) ).write_MENU( request )
+
+ request.write( " | " )
+ request.write( " " )
+ request.write( " " )
+ request.write( " | \n" )
+ request.write( " | " )
+ request.write( " " )
+ request.write( " " )
+ request.write( " | " )
+ request.write( " | " )
+
+ modName = getVar('mod', request)
+
+ if modName is None:
+ request.write( ' Please select a module ' )
+ else:
+ module = self.modules[ modName ]
+ if module:
+ module( self.mainUrlWriter( modName ) ).write_BODY( request )
+ else:
+ request.write( 'Invalid module. Please select another ' )
+
+ request.write( " | " )
+ request.write( " " )
+ request.write( " | \n" )
+ request.write( " | " )
+ request.write( "
\n" )
+
+ request.write( "
\n" )
+
+
+ def op_destroy( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != "0":
+ server.xend_domain_destroy( int( dom ), "halt" )
+
+ def op_pause( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != "0":
+ server.xend_domain_pause( int( dom ) )
+
+ def op_unpause( self, request ):
+ dom = getVar( 'dom', request )
+ if not dom is None and dom != "0":
+ server.xend_domain_unpause( int( dom ) )
diff --git a/tools/python/xen/sv/MigrateDomain.py b/tools/python/xen/sv/MigrateDomain.py
new file mode 100755
index 0000000000..eb7dacbdfc
--- /dev/null
+++ b/tools/python/xen/sv/MigrateDomain.py
@@ -0,0 +1,74 @@
+from xen.sv.Wizard import *
+from xen.sv.util import *
+from xen.sv.GenTabbed import PreTab
+
+from xen.xm.create import make_config, OptVals
+
+from xen.xend.XendClient import server
+
+class MigrateDomain( Wizard ):
+ def __init__( self, urlWriter ):
+
+ sheets = [ ChooseMigrateDomain,
+ DoMigrate ]
+
+ Wizard.__init__( self, urlWriter, "Migrate Domain", sheets )
+
+
+class ChooseMigrateDomain( Sheet ):
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Configure Migration", 0)
+ domains = []
+ domnames = []
+
+ try:
+ domains = server.xend_domains()
+ domains.sort()
+ except:
+ pass
+
+ for i in domains:
+ if i != 'Domain-0': domnames.append((i,i))
+
+ self.addControl( ListControl('domid',
+ domnames,
+ 'Domain ID:') )
+ self.addControl( TickControl('live',
+ 'True',
+ 'Live migrate:') )
+ self.addControl( InputControl('rate',
+ '0',
+ 'Rate limit:') )
+ self.addControl( InputControl( 'dest', 'myhost.mydomain',
+ 'Name or IP address:',
+ ".*") )
+
+class DoMigrate( Sheet ):
+ def __init__(self, urlWriter ):
+ Sheet.__init__(self, urlWriter, "Migration Done", 1)
+
+ def write_BODY( self, request, err ):
+
+ if not self.passback: self.parseForm( request )
+
+# print string2sxp(self.passback)
+
+ config = ssxp2hash ( string2sxp( self.passback ) )
+
+ try:
+ print config
+ print config['domid'], config['dest']
+ dom_sxp = server.xend_domain_migrate( config['domid'],
+ config['dest'],
+ config.get('live') == 'True',
+ config['rate'] )
+ success = "Your domain was successfully Migrated.\n"
+ except Exception, e:
+ success = "There was an error migrating your domain\n"
+ dom_sxp = str(e)
+
+ pt = PreTab( success + dom_sxp ) # sxp2prettystring( dom_sxp ) )
+ pt.write_BODY( request )
+
+ request.write( "" % self.passback )
+ request.write( "" % self.location )
diff --git a/tools/python/xen/sv/NodeInfo.py b/tools/python/xen/sv/NodeInfo.py
new file mode 100755
index 0000000000..a19ca7383c
--- /dev/null
+++ b/tools/python/xen/sv/NodeInfo.py
@@ -0,0 +1,63 @@
+from xen.xend.XendClient import server
+
+from xen.sv.util import *
+from xen.sv.GenTabbed import *
+
+class NodeInfo( GenTabbed ):
+
+ def __init__( self, urlWriter ):
+ GenTabbed.__init__( self, "Node Details", urlWriter, [ 'General', 'Dmesg', ], [ NodeGeneralTab, NodeDmesgTab ] )
+
+ def write_MENU( self, request ):
+ request.write( "Node details
" % self.urlWriter( '' ) )
+
+class NodeGeneralTab( CompositeTab ):
+ def __init__( self ):
+ CompositeTab.__init__( self, [ NodeInfoTab, NodeActionTab ] )
+
+class NodeInfoTab( GeneralTab ):
+
+ def __init__( self ):
+
+ nodeInfo = {}
+ try:
+ nodeInfo = sxp2hash( server.xend_node() )
+ except:
+ nodeInfo[ 'system' ] = 'Error getting node info'
+
+ dictTitles = {}
+ dictTitles[ 'System' ] = 'system'
+ dictTitles[ 'Hostname' ] = 'host'
+ dictTitles[ 'Release' ] = 'release'
+ dictTitles[ 'Version' ] ='version'
+ dictTitles[ 'Machine' ] = 'machine'
+ dictTitles[ 'Cores' ] = 'cores'
+ dictTitles[ 'Hyperthreading' ] = ( 'hyperthreads_per_core', hyperthreadFormatter )
+ dictTitles[ 'CPU Speed' ] = ( 'cpu_mhz', cpuFormatter )
+ dictTitles[ 'Memory' ] = ( 'memory', memoryFormatter )
+ dictTitles[ 'Free Memory' ] = ( 'free_memory', memoryFormatter )
+
+ GeneralTab.__init__( self, dict=nodeInfo, titles=dictTitles )
+
+class NodeDmesgTab( PreTab ):
+
+ def __init__( self ):
+ try:
+ dmesg = server.xend_node_get_dmesg()
+ except:
+ dmesg = "Error getting node information: XenD not running?"
+ PreTab.__init__( self, dmesg )
+
+class NodeActionTab( ActionTab ):
+
+ def __init__( self ):
+ ActionTab.__init__( self, { "shutdown" : "shutdown",
+ "reboot" : "reboot" } )
+
+ def op_shutdown( self, request ):
+ if debug: print ">NodeShutDown"
+ server.xend_node_shutdown()
+
+ def op_reboot( self, request ):
+ if debug: print ">NodeReboot"
+ server.xend_node_reboot()
diff --git a/tools/python/xen/sv/RestoreDomain.py b/tools/python/xen/sv/RestoreDomain.py
new file mode 100755
index 0000000000..be8b4f558a
--- /dev/null
+++ b/tools/python/xen/sv/RestoreDomain.py
@@ -0,0 +1,46 @@
+from xen.sv.Wizard import *
+from xen.sv.util import *
+from xen.sv.GenTabbed import PreTab
+
+from xen.xm.create import make_config, OptVals
+
+from xen.xend.XendClient import server
+
+class RestoreDomain( Wizard ):
+ def __init__( self, urlWriter ):
+
+ sheets = [ ChooseRestoreDomain,
+ DoRestore ]
+
+ Wizard.__init__( self, urlWriter, "Restore Domain", sheets )
+
+
+class ChooseRestoreDomain( Sheet ):
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Configure Restore", 0)
+
+ self.addControl( InputControl( 'file', '',
+ 'Suspend file name:',
+ ".*") )
+
+class DoRestore( Sheet ):
+ def __init__(self, urlWriter ):
+ Sheet.__init__(self, urlWriter, "Restore Done", 1)
+
+ def write_BODY( self, request, err ):
+
+ if not self.passback: self.parseForm( request )
+ config = ssxp2hash ( string2sxp( self.passback ) )
+
+ try:
+ dom_sxp = server.xend_domain_restore( config['file'] )
+ success = "Your domain was successfully restored.\n"
+ except Exception, e:
+ success = "There was an error restoring your domain\n"
+ dom_sxp = str(e)
+
+ pt = PreTab( success + sxp2prettystring( dom_sxp ) )
+ pt.write_BODY( request )
+
+ request.write( "" % self.passback )
+ request.write( "" % self.location )
diff --git a/tools/python/xen/sv/SaveDomain.py b/tools/python/xen/sv/SaveDomain.py
new file mode 100755
index 0000000000..18c9b0150b
--- /dev/null
+++ b/tools/python/xen/sv/SaveDomain.py
@@ -0,0 +1,62 @@
+from xen.sv.Wizard import *
+from xen.sv.util import *
+from xen.sv.GenTabbed import PreTab
+
+from xen.xm.create import make_config, OptVals
+
+from xen.xend.XendClient import server
+
+class SaveDomain( Wizard ):
+ def __init__( self, urlWriter ):
+
+ sheets = [ ChooseSaveDomain,
+ DoSave ]
+
+ Wizard.__init__( self, urlWriter, "Save Domain", sheets )
+
+
+class ChooseSaveDomain( Sheet ):
+ def __init__( self, urlWriter ):
+ Sheet.__init__( self, urlWriter, "Configure Save", 0)
+
+ domains = []
+ domnames = []
+
+ try:
+ domains = server.xend_domains()
+ domains.sort()
+ except:
+ pass
+
+ for i in domains:
+ if i != 'Domain-0': domnames.append((i,i))
+
+ self.addControl( ListControl('domid',
+ domnames,
+ 'Domain ID:') )
+ self.addControl( InputControl( 'file', '',
+ 'Suspend file name:',
+ ".*") )
+
+class DoSave( Sheet ):
+ def __init__(self, urlWriter ):
+ Sheet.__init__(self, urlWriter, "Save Done", 1)
+
+ def write_BODY( self, request, err ):
+
+ if not self.passback: self.parseForm( request )
+ config = ssxp2hash ( string2sxp( self.passback ) )
+
+ try:
+ dom_sxp = server.xend_domain_save( config['domid'],
+ config['file'] )
+ success = "Your domain was successfully saved.\n"
+ except Exception, e:
+ success = "There was an error saving your domain\n"
+ dom_sxp = str(e)
+
+ pt = PreTab( success + dom_sxp ) # sxp2prettystring( dom_sxp ) )
+ pt.write_BODY( request )
+
+ request.write( "" % self.passback )
+ request.write( "" % self.location )
diff --git a/tools/python/xen/sv/TabView.py b/tools/python/xen/sv/TabView.py
new file mode 100755
index 0000000000..41944715c2
--- /dev/null
+++ b/tools/python/xen/sv/TabView.py
@@ -0,0 +1,26 @@
+from xen.sv.HTMLBase import HTMLBase
+
+class TabView( HTMLBase ):
+
+ # tab - int, id into tabs of selected tab
+ # tabs - list of strings, tab names
+ # urlWriter -
+ def __init__( self, tab, tabs, urlWriter ):
+ HTMLBase.__init__(self)
+ self.tab = tab
+ self.tabs = tabs
+ self.urlWriter = urlWriter
+
+ def write_BODY( self, request ):
+ request.write( "" )
+ request.write( "" )
+
+ for i in range( len( self.tabs ) ):
+ if self.tab == i:
+ backgroundColor = "white"
+ else:
+ backgroundColor = "grey"
+
+ request.write( "%s | " % ( backgroundColor, self.urlWriter( "&tab=%s" % i ), self.tabs[ i ] ) )
+
+ request.write( "
" )
diff --git a/tools/python/xen/sv/Wizard.py b/tools/python/xen/sv/Wizard.py
new file mode 100755
index 0000000000..089d3f2e67
--- /dev/null
+++ b/tools/python/xen/sv/Wizard.py
@@ -0,0 +1,269 @@
+from xen.sv.util import *
+from xen.sv.HTMLBase import HTMLBase
+from xen.xend import sxp
+
+import re
+
+DEBUG = 0
+
+class Wizard( HTMLBase ):
+
+ def __init__( self, urlWriter, title, sheets ):
+ HTMLBase.__init__( self )
+ self.title = title
+ self.sheets = sheets
+ self.urlWriter = urlWriter
+
+ def write_MENU( self, request ):
+ request.write( "%s
" % (self.urlWriter( '' ), self.title) )
+
+ def write_BODY( self, request ):
+
+ request.write( "| " )
+ request.write( " %s |
| " % self.title )
+
+ currSheet = getVar( 'sheet', request )
+
+ if not currSheet is None:
+ currSheet = int( currSheet )
+ else:
+ currSheet = 0
+
+ sheet = self.sheets[ currSheet ]( self.urlWriter )
+
+ err = not sheet.validate( request )
+
+ if not err:
+ op = getVar( 'op', request )
+
+ if op == 'next':
+ currSheet += 1
+ elif op == 'prev':
+ currSheet -= 1
+
+ sheet = self.sheets[ currSheet ]( self.urlWriter )
+
+ if getVar( 'visited-sheet%s' % currSheet, request ):
+ sheet.write_BODY( request, err )
+ else:
+ sheet.write_BODY( request, False )
+
+
+ request.write( " |
" )
+ request.write( " | " )
+ if currSheet > 0:
+ request.write( " " )
+ if currSheet < ( len( self.sheets ) - 2 ):
+ request.write( " " )
+ elif currSheet == ( len( self.sheets ) - 2 ):
+ request.write( " " )
+ request.write( "
| " )
+ request.write( " |
" )
+
+ def op_next( self, request ):
+ pass
+
+ def op_prev( self, request ):
+ pass
+
+ def op_finish( self, request ):
+ pass
+
+class Sheet( HTMLBase ):
+
+ def __init__( self, urlWriter, title, location ):
+ HTMLBase.__init__( self )
+ self.urlWriter = urlWriter
+ self.feilds = []
+ self.title = title
+ self.location = location
+ self.passback = None
+
+ def parseForm( self, request ):
+ do_not_parse = [ 'mod', 'op', 'sheet', 'passback' ]
+
+ passed_back = request.args
+
+ temp_passback = passed_back.get( "passback" )
+
+ if temp_passback is not None and len( temp_passback ) > 0:
+ temp_passback = temp_passback[ len( temp_passback )-1 ]
+ else:
+ temp_passback = "( )"
+
+ last_passback = ssxp2hash( string2sxp( temp_passback ) ) #use special function - will work with no head on sxp
+
+ if DEBUG: print last_passback
+
+ for (key, value) in passed_back.items():
+ if key not in do_not_parse:
+ last_passback[ key ] = value[ len( value ) - 1 ]
+
+ self.passback = sxp2string( hash2sxp( last_passback ) ) #store the sxp
+
+ if DEBUG: print self.passback
+
+ def write_BODY( self, request, err ):
+
+ if not self.passback: self.parseForm( request )
+
+ request.write( "%s
" % self.title )
+
+ previous_values = ssxp2hash( string2sxp( self.passback ) ) #get the hash for quick reference
+
+ request.write( "" )
+
+ for (feild, control) in self.feilds:
+ control.write_Control( request, previous_values.get( feild ) )
+ if err and not control.validate( previous_values.get( feild ) ):
+ control.write_Help( request )
+
+ request.write( "
" )
+
+ request.write( "" % self.passback )
+ request.write( "" % self.location )
+ request.write( "" % self.location )
+
+ def addControl( self, control ):
+ self.feilds.append( [ control.getName(), control ] )
+
+ def validate( self, request ):
+
+ if not self.passback: self.parseForm( request )
+
+ check = True
+
+ previous_values = ssxp2hash( string2sxp( self.passback ) ) #get the hash for quick reference
+ if DEBUG: print previous_values
+
+ for (feild, control) in self.feilds:
+ if not control.validate( previous_values.get( feild ) ):
+ check = False
+ if DEBUG: print "> %s = %s" % (feild, previous_values.get( feild ))
+
+ return check
+
+class SheetControl( HTMLBase ):
+
+ def __init__( self, reg_exp = ".*" ):
+ HTMLBase.__init__( self )
+ self.name = ""
+ self.reg_exp = reg_exp
+
+ def write_Control( self, request, persistedValue ):
+ request.write( "| %s |
" % persistedValue )
+
+ def write_Help( self, request ):
+ request.write( "Text must match pattern:" )
+ request.write( " %s |
" % self.reg_exp )
+
+ def validate( self, persistedValue ):
+ if persistedValue is None:
+ persistedValue = ""
+
+ return not re.compile( self.reg_exp ).match( persistedValue ) is None
+
+ def getName( self ):
+ return self.name
+
+ def setName( self, name ):
+ self.name = name
+
+class InputControl( SheetControl ):
+
+ def __init__( self, name, defaultValue, humanText, reg_exp = ".*", help_text = "You must enter the appropriate details in this feild." ):
+ SheetControl.__init__( self, reg_exp )
+ self.setName( name )
+
+ self.defaultValue = defaultValue
+ self.humanText = humanText
+ self.help_text = help_text
+
+ def write_Control( self, request, persistedValue ):
+ if persistedValue is None:
+ persistedValue = self.defaultValue
+
+ request.write( "%s | |
" % (self.humanText, self.getName(), persistedValue) )
+
+ def write_Help( self, request ):
+ request.write( "" )
+ request.write( " %s |
" % self.help_text )
+
+class TextControl( SheetControl ):
+
+ def __init__( self, text ):
+ SheetControl.__init__( self )
+ self.text = text
+
+ def write_Control( self, request, persistedValue ):
+ request.write( "%s |
" % self.text )
+
+class SmallTextControl( SheetControl ):
+
+ def __init__( self, text ):
+ SheetControl.__init__( self )
+ self.text = text
+
+ def write_Control( self, request, persistedValue ):
+ request.write( "%s |
" % self.text )
+
+class ListControl( SheetControl ):
+
+ def __init__( self, name, options, humanText ):
+ SheetControl.__init__( self )
+ self.setName( name )
+ self.options = options
+ self.humanText = humanText
+
+ def write_Control( self, request, persistedValue ):
+ request.write( "%s | " % self.humanText )
+ request.write( " |
" )
+
+ def validate( self, persistedValue ):
+ for (value, text) in self.options:
+ if value == persistedValue:
+ return True
+
+ return False
+
+class FileControl( InputControl ):
+
+ def __init__( self, name, defaultValue, humanText, reg_exp = ".*", help_text = "You must enter the appropriate details in this feild." ):
+ InputControl.__init__( self, name, defaultValue, humanText )
+
+ def validate( self, persistedValue ):
+ if persistedValue is None: return False
+ try:
+ open( persistedValue )
+ return True
+ except IOError, TypeError:
+ return False
+
+ def write_Help( self, request ):
+ request.write( "File does not exist: you must enter a valid, absolute file path. |
" )
+
+class TickControl( SheetControl ):
+
+ def __init__( self, name, defaultValue, humanText ):
+ SheetControl.__init__( self )
+ self.setName( name )
+ self.defaultValue = defaultValue
+ self.humanText = humanText
+
+ def write_Control( self, request, persistedValue ):
+ request.write( "%s | " % self.humanText )
+
+ if persistedValue == 'True':
+ request.write( "" % self.getName() )
+ else:
+ request.write( "" % self.getName() )
+
+ request.write( " |
" )
+
+
diff --git a/tools/python/xen/sv/__init__.py b/tools/python/xen/sv/__init__.py
new file mode 100755
index 0000000000..8d1c8b69c3
--- /dev/null
+++ b/tools/python/xen/sv/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tools/python/xen/sv/params.py b/tools/python/xen/sv/params.py
new file mode 100755
index 0000000000..beed647a4f
--- /dev/null
+++ b/tools/python/xen/sv/params.py
@@ -0,0 +1,3 @@
+SV_PORT = 8080
+SV_ROOT = "/var/lib/xen/sv/"
+PID_FILE = "/var/run/xen-sv.pid"
diff --git a/tools/python/xen/sv/util.py b/tools/python/xen/sv/util.py
new file mode 100755
index 0000000000..7c9ebeda80
--- /dev/null
+++ b/tools/python/xen/sv/util.py
@@ -0,0 +1,126 @@
+from xen.xend.XendClient import server
+from xen.xend import sxp
+from xen.xend import PrettyPrint
+
+import types
+
+def getDomInfoHash( domain ):
+ domInfoHash = {}
+ try:
+ domInfoHash = sxp2hash( server.xend_domain( domain ) )
+ domInfoHash['dom'] = domain
+ except:
+ domInfoHash['name'] = "Error getting domain details"
+ return domInfoHash
+
+def sxp2hash( s ):
+ sxphash = {}
+
+ for child in sxp.children( s ):
+ if isinstance( child, types.ListType ) and len( child ) > 1:
+ if isinstance( child[1], types.ListType ) and len( child ) > 1:
+ sxphash[ child[0] ] = sxp2hash( child[1] )
+ else:
+ sxphash[ child[0] ] = child[1]
+
+ return sxphash
+
+def ssxp2hash( s ):
+ sxphash = {}
+
+ for i in s:
+ if isinstance( i, types.ListType ) and len( i ) > 1:
+ sxphash[ i[0] ] = i[1]
+
+ return sxphash
+
+def hash2sxp( h ):
+ hashsxp = []
+
+ for (key, item) in h.items():
+ hashsxp.append( [key, item] )
+
+ return hashsxp
+
+def string2sxp( string ):
+ pin = sxp.Parser()
+ pin.input( string )
+ return pin.get_val()
+
+def sxp2string( sexp ):
+ return sxp.to_string( sexp )
+
+def sxp2prettystring( sxp ):
+ class tmp:
+ def __init__( self ):
+ self.str = ""
+ def write( self, str ):
+ self.str = self.str + str
+ temp = tmp()
+ PrettyPrint.prettyprint( sxp, out=temp )
+ return temp.str
+
+def getVar( var, request, default=None ):
+
+ arg = request.args.get( var )
+
+ if arg is None:
+ return default
+ else:
+ return arg[ len( arg )-1 ]
+
+def bigTimeFormatter( time ):
+ time = float( time )
+ weeks = time // 604800
+ remainder = time % 604800
+ days = remainder // 86400
+
+ remainder = remainder % 86400
+
+ hms = smallTimeFormatter( remainder )
+
+ return "%d weeks, %d days, %s" % ( weeks, days, hms )
+
+def smallTimeFormatter( time ):
+ time = float( time )
+ hours = time // 3600
+ remainder = time % 3600
+ mins = remainder // 60
+ secs = time % 60
+ return "%02d:%02d:%04.1f (hh:mm:ss.s)" % ( hours, mins, secs )
+
+def stateFormatter( state ):
+ states = [ 'Running', 'Blocked', 'Paused', 'Shutdown', 'Crashed' ]
+
+ stateStr = ""
+
+ for i in range( len( state ) ):
+ if state[i] != "-":
+ stateStr += "%s, " % states[ i ]
+
+ return stateStr + " (%s)" % state
+
+def memoryFormatter( mem ):
+ mem = int( mem )
+ if mem >= 1024:
+ mem = float( mem ) / 1024
+ return "%3.2fGb" % mem
+ else:
+ return "%7dMb" % mem
+
+def cpuFormatter( mhz ):
+ mhz = int( mhz )
+ if mhz > 1000:
+ ghz = float( mhz ) / 1000.0
+ return "%4.2fGHz" % ghz
+ else:
+ return "%4dMHz" % mhz
+
+def hyperthreadFormatter( threads ):
+ try:
+ if int( threads ) > 1:
+ return "Yes"
+ else:
+ return "No"
+ except:
+ return "No"
diff --git a/tools/sv/Makefile b/tools/sv/Makefile
new file mode 100644
index 0000000000..06aa7c50ef
--- /dev/null
+++ b/tools/sv/Makefile
@@ -0,0 +1,2 @@
+
+all:
diff --git a/tools/sv/images/destroy.png b/tools/sv/images/destroy.png
new file mode 100755
index 0000000000000000000000000000000000000000..9545fc4837b958ef6e146c2c5f21e30258e8d8ad
GIT binary patch
literal 2408
zcmW+&3pCW(AHTnOV`e-WljqE+)}xY3b*OkTHvM@GUZ7BG^0G0CcHdaRb*QYvXuMV7%Xv1Mx>gW5WS^{}Dkr!|QNp
zM7VEw=>DXb*6=L=5WaYMvi%dg1z(qz+|YH3o_j~IaDl$wL__kNw`TF%f@R3V5R|k>+#RL+i|09Q3e`Zp)$g0J4vpJ08|#=
zNv@8_$;%Ve$BmMkUf&FPEMR>7cPfO~(A(P^aU5yl`G2tMG!QCNEBh*@^&{oEN4hUc
zzC8lGH^v1A2S-Q6#KhPGA2yE9hGI8v{KTR)<6zo`H$)6$!_ay<1CCCfY|f^K4<||~
zil%wP27>`U<AG+%(7q+lK=$sL=B34*(
zb@A(b)AY=U4~`%DL{?MNCabE_`c#mWMMN`~;4mWPntcBm(jO<6`ZU9>`Q@r%fFi8S
zZb;H7u}04(9-drk^OWn=(oX1I8{z8zyd}F_O<_&_Xq7;usxeg2t4px_y
zrTZEp5G+d`(W$;F9JApO;hAOf_^^)Gukp=#Ez4x?Xv#6xf^kjABeJ(6zx~~5dm~kC
zSo1nz6GTQxpk?dPOmj=Qc;K9!b)6Gj^sckQV
z>ykJP54Pr8VZwB%!HNoV?>r2FVOe&(Jo@wWpjl9owtu5|hAhw3`DA3>#Ui^M{l~27
z%C>ApE%v#?fC`<2^N)s^wIl*R>oo}52`TXeMtw7EVd#17F1^JM0gkV0vwC%^=H*|%
zZ7^=Tbo1Aoge?XN$!j}
zdj`>4T0UKV4b45sFi|~)j^*^y@XWs@P`*>8>7PK4wWH(Cly;Ww;h;pRL?U7D*pbB!
z2%t{#c^1&*dwARecTf}?3RG5ExK02)!Pt^YB7Z`quKvZ@*%?Qp(W2QgQ++PdJX8Io
z!>CLg5<7oAW#fr2@r)ZYRO`DeTOFPO+R#8|kR4E(>NFY%CtLz7Pz!ABQRlY~y~;G_
zc~Je+o4ONa$%J8hUX)mhi22g8y~q8ADg1MWI6c-{7Jh8EK-+ps7#S&!%-I$VW#
zVhqRb;1upH2s2ro6<%i#@ElsYyG?rkX5iqU4~Uiqx|E$)wi%bYp73_ah9=y(h;@kK
z<0nz)d6&zCuR1%Sx^_jvvMS(je@G~V_gcubivwEq8E_z6y29q9SfE82FF>taxiZ=Z
zqneX(YL}3w3gL`c{cAy7ZuaWZREQ@z93IX&otF!J-zi_Q*h!R~dyZK`252MJr$}Vps!1#R1^eCaCU9mAdv3fH2p6
zic<9Q_X$ZX+LZt)DAp`Tf}|~EarGj8q;s)&JpSz#+<1bwoyZ51
z$t|C=kARF|*hB>R_3QH46coY?sZ>gb6I#so%@g}EP#sj+z7;!-RTaxS=#9h$Q{f|A@Pr
zhQ=+DoSlD9V7juvW0wO0!3?nOOt&pb+j=PS=S&zig;)or2%1!AFDxv$!5@(%hffA?
z-Fm=Y7-inTfF!G0;_jgK+ziHM2j}YaH5|L=&1OU8Q)nbuVM8}}e0;n{@rTIN#Va-A
zN>vV?354D1&mZ3v`0L*k-?(}jF-mJJ1~IiAU!6B^o`OVvmQen1$n5Z~xY1qJlvXA%
zx!wQp#OA&tA9tWqB$kwx_GPRi>#+hOer#%NtN=ZkZ`DP{1?^ooLv(5MG76m6cRyjm
zy4p&NRc1_1MqT&N04P0<9OUPB`*cakH=QrQs#4uwAG5Nl+~-0IEgjnFeLc9L__7Lr
zKg>!lZFJ5>fJ(gJG1+sEJA@RIcDB9bFq7inQCL(Y#)oH)txBD-uADtKH*UzO{Wh-|
ztz&UV(lauK_K_hy7G}8|XwD71eQ>lN(loj8Crl3!(<~d=%XDA7c)@RPRNAr1*9fZ#
z!}=Y)%gYU2T5hn8MuGEvUi$j_iKhyXYNGyOn?pp~MVrBuID}g|(aP|`lUEsK8&9lzch?;
zcc&`XlU>g2@K`RUeIZP~ZML$sbY(P~!QgV@*jo9zAv~2_qu6wl8fX=R>WQkBHDd0z
zoGvVce(F%T54y_j5+HRED$`KkW_*RmVu
literal 0
HcmV?d00001
diff --git a/tools/sv/images/finish.png b/tools/sv/images/finish.png
new file mode 100755
index 0000000000000000000000000000000000000000..6c5d18a9b7f6091f3a92723f27029b7629ee45e0
GIT binary patch
literal 1189
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%0A6NzijcRzFf*mp+LL!lq_vW2PcfkZ&&=j
zR(8uW#a*J;D{%coEvr_w8n2Q>{2BFA$&eLI-Fs?qw0ZTWkk8=Pp7Jk$WiT?d_#-)TEL;k*h47R4J$8bf00bO?u$+y7y1&kxC_q8o6AK8lETX
zsJ#Wca2uLh+7r1T=hdpe&eD9wp4=a&<#Nu{J!4nsi;oTe&!
z0K_g)1owN@{#rl;k>9r$Ax_6+OZJ!cOOMagF8gz5gS{~1|MrB{@Ra2s`n*uj?SJdq
zv*LFRP?<(60nByT8kiW;DNoQ7ukSy8+diT_@-&(e;ESXb?`hdh)*>t&W`?Way<)O7
z%PW&tMaRq8*+nTNBMyudXGHjrlu%dgOJ4Fid0sD+Zn#H1)=814`*3;T0W}WIR6b^w
zy$61@K<*A_pTi#4Qv!%ns<@&X&j{c2I*7cP7rC?pr8UX&FR!*!&K;>-`=H&B)=Gz~
zChhAXb*w2D&KEpk2dSAVkh`h$uau@s&fOF+;7A2;az!i+C+^NcdHq{`lw<(|9J2UF
zda`hhi3#%Ovc#(+HSTSd@y&)~!2z%2uJuj9b_gw0h
z>C{k#Tw(Jn%X85uIEK#ysiC
zLKHjx@yIk6J}67k@9^uw%v=_;MfzA)9Ff*6^o8GP!GH%50{@x6YT5@?%KEcHYVi4VdE3#
zqo-aOERJhi9M6jO2ahejG`TfF0q?*wTY&rswZ}X1Gi~chGQ0I#*!TWRIJmiqTx-@olz&E9oUP>4*7;i
z(|sb*z>LfZlCKI@GVsgO1TXGb4{Y}Z4xdhnrG~m47Y_t*tY0ZTh(?Y&?KvMzs>QgN
zfpdSM59{xv-;F%G{6;V1%luk%WlDf#RfFYkJ^_C}ul1&(QU|6i4G}|LhsvsFqZsU5
zzoF=luPI4gSL%3Rr@KN=ahAD)NjX6&T^B6AA;ueyL#uJ7CU#KAa;BCq!l{%)uRqTdh
z1ALvT7O%U;MErcHyWV6h^L~lGI3X2wI(S{ecrZntIX{;1Es8wZ08JJiX)pk<|(M|clpJ8M;bm*0V?F%
zhhUSuza$igQ+8C^dKH3=WH(;`Hjo$(yQo}Z1x35$R&m=I6`=m6Ku|Bx)+}6LbYf|S
zCW$hpUU>PCD1!dIn@r1<%KF9f{X9Ek<47ht`^$X&r_c%EB`=r9>)`_`{d6gjkO1P>
z^qye0wNozh>hEcT~x)Kf%?8XM(C}p@|i57P=eniPTvH
zeB|O~vCRc@(+50UNR^a(KkLYXfztT;C`8h%4xd^rJxVzIc>7P@5KUx7C1tk^=ryow
zl?}PHeYM&WOAV19##s8Ylo;9S#&q_&plP9R_0!9}Mn>UZAV#lKg9I~v&s-_!MGXdV
zhwqk17n=6fHfKAXI2AIsuIN$jWv#qx<>7WK-DyS<%IN0Up=-2Bm!C9R7y8A@y{N
z8@AhOnzIYCO1-}A&6VBdQPOPDC}acC1hlJDud5raW}t6%kZZkIvinQ2!d+v|L#4PP
z!&Bf5flX)fHTWtPsO@GZXZ|`-fA(p$9`FcJag^?SBg_?9i{9@xmF=9o8==`-HK>F>
z_Fd0N7JCY(|4Z^>sWo9n#@Fx7J+2IEWYTe0;yGhv+2+h-CqFGt?qj5Pq>C7Ho@#Ts
zO{k6{RI<&dQ$+5Y1S&*r!NoA6WQNi*GR+>T)U{)114f#}PbYTITjy%1Lmm1`6<~}7
z?dEwUwWzNhLi`^GNBL*SJle4h`-~z--Gaqs*okqgrEA4TGIfx1xFw;X8E3!5?R#6g
zIm-d<(`xK`=dP+<#i(03nYyt)z?MMnS;^#K@Am_m9lCT3vH@U!5K4~v=V%EP1Xzt1
z@;?3X(rv?!syPlMPyvJ&!V^V_RpZ0@6Pz9qM^KuNu`0maTd3E
z%r@4*^{tDe6ER-fJHe{Il~=^(m*uv@W6c#qO7;VeO$(tFcKE1lZ2STmX>i-)$Cywv
zV;amB8`v^pfO
zDBhKwE&Y-fwAP)X2;vbTr{yBG%CVh-5yIKhm6cHlLFJxD_udkG0lxdLt-$2AZ)6FP)Hh`q<+yqdEcBpwXqBQ5XJ8>IEny%aJ3^
z4^}##Ir!SlaIBPsCkG{M4M9@fcMAxKT%vn}BljinXygxnsQNG51AVnHG~ZbkCl$z7Qr9!8BWVf_)QT|IwE(kYk@a|>FU
z_y!h17N4JRq`uI27D>^dBa_5;d3Jx)N!_>0(1WUj;LTXzsR8kSYed)HxBgYq&*Wf{KFU)&uQtbBluz`@BYRTx6IM8H<@DllW58O!$LL%`R4MP=2NLQ`
z$HVDv9`)6)dOs%Ufgplf(cOpRvq;X`8V{jMl5Z3t@QDhe0w1(jv5~Dr0w-l*i=RjK=UrLX$
zE|+L8iG87!mpRJq3L4kCb&`wKcvXCrvlSA%Njp0wl*nHlhF0ieFM_MWOk>NsqV{GR#4)=3@9ZtqYMQd
z-VOS0Q23+Sr6Fb9X}NW2F=udPsO&FE!eN1r6aj@Mb#S(BXD
zSgFSqA#Ni@72YOAse^p0laSWEqfm2_pAH
zH?vBy5c(RYqNRes?~a3M0(-Gj6nZq0M!v^3SGn+cCuJWaZ6AcIfm{R9gJ*c3awl#^
zJG7b9U@U94hfLtQtMOIS(VgO@UX8*DAhXY+x_?PNftvLyHCeQ{yGpCM1`&-jdjOtZ
zu_&xGVLYabL**Cr>}8vNBFd?xjqeg-Gzxe;vQU@wU3Z9L;hra__{y}Ptb3Z|1~O7w
zLcBRPt}J`@_Kvw;D;FH_pc~Z-o6G`SG|%mw7efDprOp|z?g2mgcuDRDWqn1XfDz;>
zQCdS-gx7&5+Lr_G5}){k+jZ^z`!aA^2gm9CSI*1~v^w0bZLsQP5>Mg3B;-AwwDu_O
zvS_-=)BD$PsNWOjc%_1vbUNVZe1#V$mc_4T%wOz-`~U{~g-&D6ExheQQ@9nx7@4^A
zC-mh1V(&epqH4OW(IzQDkW7Ps0+M5c7-OY;#?^&oV?YR&cUSL9b+Hnq^0Q%Dfof9WD9>OK`Q9D5C_{2z%ef&Hb`yq;HvSu>Mb+
znRu&*Bd$^)*a?`w;{xE^zwAl%c}tr=kAEISsd1zD#xI;*yW3+tCoTRKc*l%5TlK|8
zGfrA82Q0&ZTZIdM!$1#{sIO#m4G4B3RF2w=tr}n0f9T^;gAXM%U(L|uiYe;7_)~A1
z#AdwZWFzhdnoA7?I^3~8OkhZ6bQO1F*Z
zk`&~%&s8
zdcA}9R4ZR$NcqTuf!O0(W!*OCTCd9+@AU;pYfP?#0L&IVj^(+kA%#*WG?mTA`h9LT
z;s@v~vsBZhMFIFoO&xFa?ZCeA^t9pGJ*B8J9gQ@10HWDM@5wqJBq)A42v>zWi
zO?C0K>b}^kRql^fEfn)-YZ1bh^@xi6YOYoz)NyIu^~a?ZAQ8$(grsalJFQ)**DK3&
z@b$W$&p6Tj=LosfntmSYxLRK_#CKL#@N+6B78bAnithjt};kA0*dS
zEl7tn$H4&gDH-Xwj&4A|z*00kj*q^f-Qe9>HvEG=cl<8~jX<2UJ8u#tJVf}$K
zTY;cgwhNiQB1poE*A57H(Av*XqhN_~6CW
z5DquQ;
zG<;zIh_vg>;Y}+tE$X_L>fS^MWj4^3z=~mUy%XXNi`g5Y68E<7BDpwnSdw<+$#UaQ
zM_*RNMlLM#&SX=?V#gxQsoF~3JWQ6-u%3DB%|L1<>XljdV?SY=aU%7m+lG&r>`l}!
zEBiS^G|s8@gmhmgD8Rq>bGAilhVs3>$4X6B+*+kfqD$4w
zE{oq;94IMO9$AmYICB^Au6qM{Hu*Lqz7NbG$V6Zdj$+Vouf)ACrt{SI0_3rl{86aK
z#m)sh3V68<-=#I7D_R#6?0P=e%&qu7E#WTgIj$7@)RrdITrmRmW!3ssg4E^Z&i%l3
za)HXo&@{kJ<`vHgo8BTF+F|V>-Bb6=b=RCM1KI-8y=AW$uQ)7bEZJD-G2-|P28wN5
zBVxMo&J&pqwDGq;{tQq)mtbTAlNAABv@?a
z!dzHdrDH%GwRsE}Of_HQWeKuGvP9-r(;x(AYR0LQZ-;j)c*9cuSBg(f?rrL9c+h-m
zkj%lh5&*?rquZpZ?~d8MAm}DoX5?5O#?+Z@SWieF^30<#;FK`qQ#zyxBJx~n
zb`rjGy9<6JyBz}pnxZ|aKK_`|!7q&-QU_p-4B+-obV6RvJv`jjRy
zcXz;-(Cg4f)bHo9Z%X)TdR$yt9MVbD6U2?@i@sYqL)V|(_O;S?4slCdQ3xI5vRc|{iHECS59dvK8rdlcv3J^)gHbnx+J@ip(xmsebh>I)es`ZTv@xFuoq?*3ZzDc&wTRq3
zN0ynSh)MV|Eokpu!g5$_`Xl?SfK%-mKjd_+x7?lO)t30x^jERZY^P@4qBp2@c}~dj|8LXHL9n1tw~1O{^4vpl=aX^)xWP}uOnqr&2+L_8oZpk
z%G}eu^q`v=JNmh1=#T0=Sqq